<template>
  <div
    :id="DOMId"
    v-loading="isVisualVappShowLoadingProgress && isLoading"
    class="hover-text-box"
    :class="componentStyle"
    :style="styleObj"
  >
    <div style="height:100%;" class="hover-text-box-inner" @dblclick="dblclickEvent">
      <div v-if="!html && isDesignView && !isDataAbnormal" class="hover-text-default">
        <span>{{ $demoi18n('word.hoverTextDefault') }}</span>
      </div>
      <Tooltip ref="Tooltip" :component-style="componentStyle" />
      <TinymceEditorHtml
        v-if="!isDataAbnormal"
        v-visual-tooltip="getTooltip()"
        :html="html"
        :extra-config="styleParams"
        @click="clickHtml"
      />
      <div v-if="isDataAbnormal" :style="{ height: '100%', display: 'flex', ...boxBgColor, 'align-items': 'center' }">
        <AbnormalData :isDataAbnormal="isDataAbnormal" :boxBgColor="boxBgColor" :abnormalType="abnormalType" />
      </div>
    </div>
  </div>
</template>
<script>
import TinymceEditorHtml from '@/components/common/tinymce-editor/tinymce-editor-html.vue';
import chartBase from '@/mixin/chart-base.js';
import dataModel from '@/models/panel-config-data.js';
import { CAN_AUTO_REFRESH_TYPE } from '@/utils/constant.js';
import { adjustFontSize, convertValue, evalFormula, numFormat, permil, xssValueFilter, _xss, isEmpty } from '@/utils/util';
import whiteList from '@/utils/xss/tag-white-list';
import { mapMutations, mapState } from 'vuex';
import Tooltip from '../common/tooltip/tooltip.vue';
import config from './config/config';
import EventBus from '@/event-bus/EventBus.js';
import AbnormalData from '../common/abnormal-data/abnormal-data.vue';

export default {
  name: 'VisualComponentsHoverText',
  components: { TinymceEditorHtml, Tooltip, AbnormalData },
  mixins: [
    chartBase,
    window.xvueWidgetBaseMixin || {}, // 修复：拖入卡片组件，再拖入浮动文本组件，这时候浮动文本组件不会执行loadwidget
  ],
  props: ['pareentId', 'widgetId', 'widgetInstanceId', 'widgetName', 'identifier', 'layout', 'w', 'h'],
  emits: ['hoverTextPop'],

  data() {
    return {
      orgConfig: null,
      DOMId: 'hover-text',
      componentStyle: window._adoPremise.uc.theme,
      styleObj: {},
      styleParams: {},
      isLoading: false,
      html: '',
      apiReadyCount: 0, // 已查询完毕的接口数量
      vars: {}, // 主要变量
      extVars: {}, // 扩展变量
      tooltipConfig: {}, // 配置，对应目标组件config.js下的data.tooltip属性
      apiDatas: {},
      apiVars: {},
      timer: null,
    };
  },
  computed: {
    ...mapState({
      resizeCount: state => state.VisualComponentsModule.resizeCount,
    }),
  },
  watch: {
    apiReadyCount(count, oldCount) {
      if (Object.keys(this.apiDatas).length <= count) {
        this.isLoading = false;
        this.getApiVars();
        this.replaceHtml();
      }
    },
    resizeCount() {
      if (this.tooltipConfig.html) {
        this.replaceHtml();
      }
    },
  },
  methods: {
    ...mapMutations(['setComponentStatus']),
    loadWidget(pageStatus) {
      this._updateConfigProperty();
      this.updateConfigData();

      // 修复第一次加载时因为emitComponentStatus为false时不触发api问题
      const fn = () => {
        if (window.isLocalEnv || this.emitComponentStatus) {
          this.updateParams(pageStatus);
          if (!this.isRealTime) {
            this.debounceGetData();
          }
        } else {
          this.timers.loadWidget = setTimeout(fn, 300);
        }
      };
      fn();

      if (window.isLocalEnv || this.emitComponentStatus) {
        this.updateParams(pageStatus);
        if (!this.isRealTime) {
          this.debounceGetData();
        }
      }
      this.afterLoadWidget();
    },

    // 更新部分配置的属性
    _updateConfigProperty() {
      this.updateConfigProperty();

      // 默认配置文件格式统一且都有组件风格 componentStyle
      const {
        backgroundType,
        backgroundColor,

        paddingTop = 0,
        paddingBottom = 0,
        paddingLeft = 0,
        paddingRight = 0,
        showScrollBar = true,
        alignItems = 'flex-start',
      } = this.config.config.property;
      this.styleObj = {};
      this.styleParams = {
        paddingTop,
        paddingBottom,
        paddingLeft,
        paddingRight,
        showScrollBar,
        alignItems,
      };
      this.styleObj['color'] = window._adoPremise.uc.theme === 'dark' ? 'rgba(255,255,255, 0.7)' : 'rgba(0,0,0, 0.7)';
      if (backgroundType === 'transparent') {
        this.styleObj['background'] = 'transparent !important';
      } else {
        this.styleObj['background'] = `${backgroundColor} !important`;
      }
      this.styleObj['padding'] = `${convertValue(paddingTop)}px ${convertValue(paddingRight)}px ${convertValue(
        paddingBottom,
      )}px ${convertValue(paddingLeft)}px`;
    },
    updateConfigData() {
      this.tooltipConfig = this.config.config.data.tooltip;
    },
    async handleRealTimeCommunication(realTimeData) {
      const { key: _id, value = {} } = realTimeData;
      const html = transcode(this.tooltipConfig.html);
      this.tooltipConfig.html = xssValueFilter(html);

      this.initApiDatas();
      const { apis } = this.tooltipConfig;
      if (!apis.length) {
        this.getApiVars();
        this.replaceHtml();
      }
      this.apiReadyCount = 0;

      // 没加setTimeout的情况下watch不到apiReadyCount变化
      this.timers.apiReadyCountTimer = setTimeout(() => {
        this.apiReadyCount = 0;
        for (let i = 0; i < apis.length; i++) {
          const key = this.getApiKey(apis[i]);
          const api = this.apiDatas[key];

          this.isLoading = true;
          const propertyMapping = this.getPropertyMapping(api);

          // 目前这种写法只适用于xMiner
          this.addCommonParams(api.dataSourceType);
          let params = {
            params: (this.params && this.params.params) || [],
            apiCode: api.apiCode,
            propertyMapping,
          };
          params = this.getCorrectParams(params, api.dataSourceType);

          // 公用数据库增加传参
          if (this[`addCommonSourceParams${api.dataSourceType}`]) {
            params = this[`addCommonSourceParams${api.dataSourceType}`](params, api);
          }
          this.apiDatas[key]['result'] = value['tooltip_data'][key];
          this.apiReadyCount += 1;
        }
      }, 20);
    },
    async getChartData() {
      const { html } = this.tooltipConfig;
      if (!this.isChildComponent) {
        this.tooltipConfig.html = xssValueFilter(html);
      }
      this.getApiDatas();
    },

    // 使用xss过滤对象中的值
    xssFilter(obj) {
      const { showThousand } = this.config.config.property;
      Object.entries(obj).map(([key, val]) => {
        obj[key] = _xss(showThousand ? numFormat(val, true) : val, {
          whiteList,
        });
      });
      return obj;
    },
    replaceHtml() {
      let { html } = this.tooltipConfig;
      html = adjustFontSize(html);
      const apiVars = this.xssFilter(this.apiVars);
      const vars = { ...apiVars, ...this.vars, ...this.extVars };
      Object.entries(vars).map(([i]) => {
        const regex = new RegExp(`\\$\\{${i}\\}`, 'g');

        // 无数据展示
        if (isEmpty(vars[i])) {
          vars[i] = `{${this.$demoi18n('message.noData')}}`;
        }

        html = html.replace(regex, vars[i]);
      });

      // 替换计算公式
      const arr = html.match(/\${[^}]+}/g); // ${a+b*d-e/f}
      if (arr) {
        for (let i = 0; i < arr.length; i++) {
          let formula = arr[i].replace('${', '').replace('}', '');
          Object.entries(vars).map(([j, val]) => {
            const regex1 = new RegExp(j, 'g');
            formula = formula.replace(regex1, vars[j]);
          });
          try {
            let val = evalFormula(formula);

            // 无数据展示
            if (isEmpty(val)) {
              val = `{${this.$demoi18n('message.noData')}}`;
            }

            while (html.indexOf(arr[i]) !== -1) {
              html = html.replace(arr[i], val);
            }
          } catch (e) {}
        }
      }

      // 显示千分位
      const { showThousand } = this.config.config.property;
      if (showThousand) {
        html = permil(html);
      }
      this.html = xssValueFilter(html, 'editor');
    },
    getApiVars() {
      const mainVars = { ...this.vars, ...this.extVars };
      let apiVars = {};

      for (let i = 0; i < this.tooltipConfig.apis.length; i++) {
        const api = this.tooltipConfig.apis[i];
        const key = this.getApiKey(api);
        const res = this.apiDatas[key].result;
        const vars = res && res.length && res[0];
        const newVars = {};
        if (vars) {
          Object.entries(vars).map(([varKey]) => {
            const field = api.apiFields.find(f => String(f.option_value) === String(varKey));
            if (field && field.option_label) {
              const key1 = `v${i + 1}.${field.option_label}`; // v1.名称(新) v1.name (旧)
              newVars[key1] = vars[varKey];
            }
          });
        }
        apiVars = { ...apiVars, ...newVars };
      }

      this.apiVars = apiVars;
    },
    getApiKey(api) {
      return `${api.dataSourceType}__${api.dataSource}__${api.apiCode}`;
    },
    getPropertyMapping(api) {
      const map = {};
      api.apiFields.forEach(n => {
        map[n.option_value] = n.option_value;
      });
      return map;
    },

    initApiDatas() {
      const { apis } = this.tooltipConfig;
      for (let i = 0; i < apis.length; i++) {
        const key = this.getApiKey(apis[i]);
        if (!this.apiDatas[key]) {
          this.apiDatas[key] = {
            ...apis[i],
            result: null,
          };
        } else {
          this.apiDatas[key] = {
            ...this.apiDatas[key],
            ...apis[i],
          };
        }
      }

      Object.entries(this.apiDatas).map(([i]) => {
        const api = this.apiDatas[i];
        const hasApi = apis.some(n => n.dataSourceType === api.dataSourceType && n.dataSource === api.dataSource && n.apiCode === api.apiCode);
        if (!hasApi) {
          delete this.apiDatas[i];
        }
      });
    },
    autoRefresh(api, key) {
      this.isLoading = false;
      const { autoRefresh = {}, dataSourceType, dataSource, apiCode } = api;
      clearTimeout(this.timers[`autoRefresh_${dataSource}_${apiCode}`]);
      const canAutoRefreshType = CAN_AUTO_REFRESH_TYPE.includes(dataSourceType);
      if (canAutoRefreshType && autoRefresh.switchEnable && autoRefresh.actType === 'pull') {
        this.timers[`autoRefresh_${dataSource}_${apiCode}`] = setTimeout(() => {
          this.getDataBySingerApi(api, key, true);
          this.autoRefresh(api, key);
        }, autoRefresh.interval * 1000);
      }
    },
    getApiDatas() {
      this.initApiDatas();
      const { apis } = this.tooltipConfig;
      if (!apis.length) {
        this.getApiVars();
        this.replaceHtml();
      }
      this.apiReadyCount = 0;

      // 避免有api的删减，先置空autoRefresh的计时器
      const apiTimers = Object.keys(this.timers).filter(item => item.includes('autoRefresh_'));
      for (let a = 0; a < apiTimers.length; a++) {
        clearTimeout(this.timers[apiTimers[a]]);
      }

      // 没加setTimeout的情况下watch不到apiReadyCount变化
      this.timers.apiReadyCountTimer = setTimeout(() => {
        this.apiReadyCount = 0;
        for (let i = 0; i < apis.length; i++) {
          const key = this.getApiKey(apis[i]);
          const api = this.apiDatas[key];

          this.isLoading = true;
          this.autoRefresh(api, key);
          this.getDataBySingerApi(api, key, true);
        }
      }, 20);
    },
    getDataBySingerApi(api, key, addApiCount = false) {
      // 使用父组件传过来的数据
      if (api.useParentData) {
        this.apiDatas[key]['result'] = this.resData || [];
        addApiCount && (this.apiReadyCount += 1);
        return;
      }

      const method = `getChartData${api.dataSourceType}`;
      const propertyMapping = this.getPropertyMapping(api);

      // 目前这种写法只适用于xMiner
      this.addCommonParams(api.dataSourceType);
      let params = {
        params: (this.params && this.params.params) || [],
        apiCode: api.apiCode,
        propertyMapping,
      };
      params = this.getCorrectParams(params, api.dataSourceType);

      // 公用数据库增加传参
      if (this[`addCommonSourceParams${api.dataSourceType}`]) {
        params = this[`addCommonSourceParams${api.dataSourceType}`](params, api);
      }
      const cacheMs = this.isChildComponent ? 1000 : 0;
      dataModel[method](params, api, null, cacheMs)
        .then(res => {
          // 重置数据异常状态
          this.resetAbnormalType();
          this.reportIfFieldsError(this.params.propertyMapping, res);
          this.apiDatas[key]['result'] = res;
          addApiCount && (this.apiReadyCount += 1);
        })
        .catch(err => {
          this.handleChartDataError(err);
        });
    },
    getTooltip() {
      return {
        config: this.config.config.property.tooltip || {},
        vars: {},
        params: this.params,
        parentComp: this,
      };
    },
    dblclickEvent() {
      const { timer } = this;
      if (timer) {
        window.clearTimeout(timer);
        this.timer = null;
      }
      this.timer = setTimeout(() => {
        this.openHoverEditor();
      }, 300);
    },
    openHoverEditor() {
      EventBus.$emit('hoverTextPop');
    },
    clickHtml() {
      this.executeAction();
    },
  },
  created() {
    this.DOMId = `${this.DOMId}_${this.widgetInstanceId}`;
    this.orgConfig = config;
    this.config = JSON.parse(JSON.stringify(this.orgConfig));
  },
};
</script>
<style lang="less" scoped>
// 图表通用样式，主要有黑色loading 和 黑色 ‘没有数据’，样式名需一致
@import '../../common/chart.less';
</style>
<style lang="less" scoped>
.hover-text-box {
  width: 100%;
  height: 100%;
  position: relative;
  margin: 0 auto;
  &.dark {
    .el-loading-mask {
      background-color: '#0D263A';
    }
  }
  .hover-text-default {
    position: absolute;
    top: 50%;
    left: 50%;
    transform: translate(-50%, -50%);
    cursor: pointer;
    z-index: 2;
  }
}
</style>
